/*
 * @Author: zhangguliu1234 984556209@.com
 * @Date: 2024-01-16 10:52:38
 * @LastEditors: zhangguliu1234 984556209@.com
 * @LastEditTime: 2024-03-08 11:13:28
 * @FilePath: \BED2\components\ota.c
 * @Description:
 *
 * Copyright (c) 2024 by ${git_name_email}, All Rights Reserved.
 */
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_ota_ops.h"
#include "esp_http_client.h"
#include "esp_https_ota.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "esp_wifi.h"
#include "ota.h"
#include "sdkconfig.h"
#include "my_wss.h"
#include "BedSpread.h"

#define CONFIG_EXAMPLE_OTA_RECV_TIMEOUT 5000

int Run_Version = 0; // 用来记录运行版本的十进制数字

static const char *TAG = "http_ota";
extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start");
extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end");

char esp32_ota_link[80] = "http://192.168.110.121:8070/build/main.bin";
char g_running_app_version[20] = {0};

static esp_err_t _http_client_init_cb(esp_http_client_handle_t http_client);

void update_app_version(char *running_app_version)
{
    if (strlen(running_app_version) > sizeof(g_running_app_version))
    {
        ESP_LOGE(TAG, "Len of running_app_version error:%d", strlen(running_app_version));
    }
    else
    {
        memset(g_running_app_version, 0, sizeof(g_running_app_version));
        strcpy(g_running_app_version, running_app_version);
    }
}

void ota_init(void)
{
    esp_err_t err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        // 1.OTA app partition table has a smaller NVS partition size than the non-OTA
        // partition table. This size mismatch may cause NVS initialization to fail.
        // 2.NVS partition contains data in new format and cannot be recognized by this version of code.
        // If this happens, we erase NVS partition and initialize NVS again.
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }
    ESP_ERROR_CHECK(err);

#if defined(CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE)
    /**
     * We are treating successful WiFi connection as a checkpoint to cancel rollback
     * process and mark newly updated firmware image as active. For production cases,
     * please tune the checkpoint behavior per end application requirement.
     */
    const esp_partition_t *running = esp_ota_get_running_partition();
    esp_ota_img_states_t ota_state;
    //// 获取当前正在运行的固件分区。
    if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK)
    {
        // 如果当前固件的状态是 ESP_OTA_IMG_PENDING_VERIFY（等待验证）
        if (ota_state == ESP_OTA_IMG_PENDING_VERIFY)
        {
            // 被调用来标记当前应用程序为有效，并取消回滚。
            if (esp_ota_mark_app_valid_cancel_rollback() == ESP_OK)
            {
                ESP_LOGI(TAG, "App is valid, rollback cancelled successfully");
            }
            else
            {
                ESP_LOGE(TAG, "Failed to cancel rollback");
            }
        }
    }
#endif
    // 获取当前运行应用程序的分区信息
    const esp_partition_t *running = esp_ota_get_running_partition();
    esp_app_desc_t running_app_info;
    static int num1, num2, num3;
    if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK)
    {
        ESP_LOGI(TAG, "Running firmware version: %s", running_app_info.version);
        sscanf(running_app_info.version, "%d.%d.%d", &num1, &num2, &num3);
        Run_Version = num1 * 100 + num2 * 10 + num3;
        printf("Run_Version = %d\n", Run_Version);
        update_app_version(running_app_info.version);
    }
}

void ota_mcu_start(char *ota_link)
{
    memset(esp32_ota_link, 0, strlen(esp32_ota_link) - 1);
    strcpy(esp32_ota_link, ota_link);
    printf("进入ota_mcu_start\n");
    xTaskCreate(&advanced_ota_example_task, "advanced_ota_example_task", 1024 * 8, NULL, 6, NULL);
}

static esp_err_t _http_client_init_cb(esp_http_client_handle_t http_client)
{
    esp_err_t err = ESP_OK;
    /* Uncomment to add custom headers to HTTP request */
    // err = esp_http_client_set_header(http_client, "Custom-Header", "Value");
    return err;
}

void advanced_ota_example_task(void *pvParameter)
{
    ESP_LOGI(TAG, "Starting Advanced OTA example");
    esp_http_client_config_t config = {
        //.url = CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL,
        .url = esp32_ota_link,                     // URL 地址，用于指定固件升级的服务器位置。
        .cert_pem = (char *)server_cert_pem_start, // 用于指定服务器的证书
        .timeout_ms = CONFIG_EXAMPLE_OTA_RECV_TIMEOUT,
        .keep_alive_enable = true, // 用于启用 HTTP 连接的保持活动功能。
    };
    esp_https_ota_config_t ota_config = {
        .http_config = &config,
        .http_client_init_cb = _http_client_init_cb /*注册一个回调，以便在初始化 esp_http_client 后调用*/
    };

    esp_https_ota_handle_t https_ota_handle = NULL;
    /*启动 HTTPS OTA 固件升级必须首先调用此函数。如果此函数成功返回，那么应调用 `esp_https_ota_perform` 来继续 OTA 过程，
     * 并且在 OTA 操作完成或在后续操作失败时应调用 `esp_https_ota_finish`。*/
    esp_err_t err = esp_https_ota_begin(&ota_config, &https_ota_handle);
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "ESP HTTPS OTA Begin failed");
        if (err == ESP_FAIL) // 通用失败。
        {
            printf("通用失败\n");
            ESP_LOGE(TAG, "ESP HTTPS OTA ESP_FAIL");
            Publish_Reply_Message(my_client, g_publish_topic, false);
        }
        else if (err == ESP_ERR_INVALID_ARG) // 无效参数（缺少/不正确的配置、证书等）。
        {
            printf("无效参数\n");
            ESP_LOGE(TAG, "ESP HTTPS OTA ESP_ERR_INVALID_ARG");
            Publish_Reply_Message(my_client, g_publish_topic, false);
        }
        ESP_LOGE(TAG, "ESP HTTPS OTA OTHER ERROR\n");
        vTaskDelete(NULL);
    }

    esp_app_desc_t app_desc;
    /**
     * @brief 从图像标题读取应用程序描述。应用程序描述提供了图像的“固件版本”等信息。该接口只能在 esp_https_ota_begin() 之后、esp_https_ota_perform() 之前调用。调用此 API 不是强制的。
     * https_ota_handle -- [in]指向 esp_https_ota_handle_t 结构的指针
     * app_desc -- [out]指向分配的esp_app_desc_t结构的指针
     */
    err = esp_https_ota_get_img_desc(https_ota_handle, &app_desc);
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "esp_https_ota_read_img_desc failed");
        goto ota_end;
    }

    while (1)
    {
        /*此函数从 HTTP 流中读取图像数据并将其写入 OTA 分区。只有在 esp_https_ota_begin() 返回成功后才能调用此函数。
         * 由于此函数在每次 HTTP 读取操作后返回，因此必须在循环中调用，从而具有在 OTA 操作中途停止的灵活性。*/
        err = esp_https_ota_perform(https_ota_handle);
        /*ESP_ERR_HTTPS_OTA_IN_PROGRESSOTA 升级正在进行中*/
        if (err != ESP_ERR_HTTPS_OTA_IN_PROGRESS)
        {
            break;
        }
        /*每次读取操作后，esp_https_ota_perform 都会返回，用户可以通过调用 esp_https_ota_get_image_len_read，监控 OTA 升级的状态，esp_https_ota_get_image_len_read 会给出目前读取的图像数据长度。*/
        ESP_LOGD(TAG, "Image bytes read: %d", esp_https_ota_get_image_len_read(https_ota_handle));
    }

    esp_err_t ota_finish_err = ESP_OK;
    /*检查是否已接收完整的数据*/
    if (esp_https_ota_is_complete_data_received(https_ota_handle) != true)
    {
        // 未完全接收 OTA 图像，用户可针对这种情况自定义响应。
        ESP_LOGE(TAG, "Complete data was not received.");
        Publish_Reply_Message(my_client, g_publish_topic, false);
    }
    else
    {
        /*清理 HTTPS OTA 固件升级并关闭 HTTPS 连接*/
        ota_finish_err = esp_https_ota_finish(https_ota_handle);
        if ((err == ESP_OK) && (ota_finish_err == ESP_OK))
        {
            Publish_Reply_Message(my_client, g_publish_topic, true);
            ESP_LOGI(TAG, "ESP_HTTPS_OTA upgrade successful. Rebooting ...");
            vTaskDelay(1000 / portTICK_PERIOD_MS);
            esp_restart(); // 重启
        }
        else
        {
            if (ota_finish_err == ESP_ERR_OTA_VALIDATE_FAILED)
            {
                ESP_LOGE(TAG, "Image validation failed, image is corrupted");
            }
            Publish_Reply_Message(my_client, g_publish_topic, false);
            ESP_LOGE(TAG, "ESP_HTTPS_OTA upgrade failed 0x%x", ota_finish_err);
            vTaskDelete(NULL);
        }
    }

ota_end:
    esp_https_ota_abort(https_ota_handle); // 清理 HTTPS OTA 固件升级并关闭 HTTPS 连接
    ESP_LOGE(TAG, "ESP_HTTPS_OTA upgrade failed");
    Publish_Reply_Message(my_client, g_publish_topic, false);
    vTaskDelete(NULL);
}